package com.agile.leetcode.compressfile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Author:ChenZhangKun
 * @Date: 2022/9/4 20:50
 */
public class CompressFile {
    private static final double FILE_SIZE = 100; // 指定分组压缩的大小 550KB
    private static final String PATH = "D:\\yygh_log\\edu\\error"; // 指定要处理的文件夹

    public static void main(String[] args) throws IOException {
        List<FileModel> list = getFiles(PATH);

        Map<Double, List<FileModel>> map = new HashMap<>();
        getArr(list, FILE_SIZE, map);

        if (map.size() > 0) {
            for (Double aDouble : map.keySet()) {
                List<FileModel> fileModels = map.get(aDouble);
                batchZipFiles(fileModels, PATH + "" + aDouble.toString() + ".zip");
            }
        }
    }

    private static void batchZipFiles(List<FileModel> list, String zipOutPath) throws IOException {
        ZipOutputStream zipOutputStream = null;
        WritableByteChannel writableByteChannel = null;
        MappedByteBuffer mappedByteBuffer = null;
        try {
            zipOutputStream = new ZipOutputStream(new FileOutputStream(zipOutPath));
            writableByteChannel = Channels.newChannel(zipOutputStream);
            File file = new File(PATH);
            File[] tempList = file.listFiles();
            List<String> fileList = list.stream().map(FileModel::getName).collect(Collectors.toList());
            File[] addList = new File[fileList.size()];
            assert tempList != null;
            for (File file1 : tempList) {
                if (fileList.contains(file1.getName())) {
                    long fileSize = file1.length();
                    //利用putNextEntry来把文件写入
                    zipOutputStream.putNextEntry(new ZipEntry(file1.getName()));
                    long read = Integer.MAX_VALUE;
                    int count = (int) Math.ceil((double) fileSize / read);
                    long pre = 0;
                    //由于一次映射的文件大小不能超过2GB，所以分次映射
                    for (int i = 0; i < count; i++) {
                        if (fileSize - pre < Integer.MAX_VALUE) {
                            read = fileSize - pre;
                        }
                        mappedByteBuffer = new RandomAccessFile(file1, "r").getChannel()
                                .map(FileChannel.MapMode.READ_ONLY, pre, read);
                        writableByteChannel.write(mappedByteBuffer);
                        pre += read;
                    }
                }
            }
            assert mappedByteBuffer != null;
            mappedByteBuffer.clear();
        } finally {
            try {
                if (null != zipOutputStream) {
                    zipOutputStream.close();
                }
                if (null != writableByteChannel) {
                    writableByteChannel.close();
                }
                if (null != mappedByteBuffer) {
                    mappedByteBuffer.clear();
                }
            } catch (Exception e) {


            }

        }
    }

    // 返回文件大小尺寸
    private static String getFileSizeString(Long size) {
        double length = Double.parseDouble(String.valueOf(size));
        //如果字节数少于1024，则直接以B为单位，否则先除于1024，后3位因太少无意义
        if (length < 1024) {
            return length + "B";
        } else {
            length = length / 1024.0;
        }
        //如果原字节数除于1024之后，少于1024，则可以直接以KB作为单位
        //因为还没有到达要使用另一个单位的时候
        //接下去以此类推
        if (length < 1024) {
            return Math.round(length * 100) / 100.0 + "KB";
        } else {
            length = length / 1024.0;
        }
        if (length < 1024) {
            //因为如果以MB为单位的话，要保留最后1位小数，
            //因此，把此数乘以100之后再取余
            return Math.round(length * 100) / 100.0 + "MB";
        } else {
            //否则如果要以GB为单位的，先除于1024再作同样的处理
            return Math.round(length / 1024 * 100) / 100.0 + "GB";
        }
    }

    private static void getArr(List<FileModel> list, double fileSize, Map<Double, List<FileModel>> map) {
        List<FileModel> listAdd = new ArrayList<>();
        if (list.size() > 0) {
            for (FileModel fileModel : list) {
                if (listAdd.size() == 0) {
                    listAdd.add(fileModel);
                } else {
                    if (listAdd.stream().mapToDouble(FileModel::getSize).sum() < fileSize) {
                        // 分组没满
                        listAdd.add(fileModel);
                        if (listAdd.size() == list.size()) {
                            map.put(listAdd.stream().mapToDouble(FileModel::getSize).sum(), listAdd);
                        }
                    } else {
                        // 取差集
                        list = list.stream().filter(item -> !listAdd.contains(item)).collect(Collectors.toList());
                        map.put(listAdd.stream().mapToDouble(FileModel::getSize).sum(), listAdd);
                        getArr(list, fileSize, map);
                        break;
                    }
                }
            }
        }
    }

    /**
     * 拿到文件
     *
     * @param path
     * @return
     */
    private static List<FileModel> getFiles(String path) {

        List<FileModel> list = new ArrayList<>();
        File file = new File(path);
        File[] files = file.listFiles();
        if (files != null && files.length > 0) {
            for (File file1 : files) {
                if (file1.isFile()) {
                    // 是文件
                    list.add(new FileModel(file1.getName(), getFileSizeKB(file1.length())));
                }
            }
        }
        return list;
    }

    /**
     * // 获取文件大小KB
     *
     * @param length
     * @return
     */
    private static Double getFileSizeKB(long length) {
        double size = Double.parseDouble(String.valueOf(length));
        return size / 1024.0;

    }
}
